#include "klist.h"

#include "sysapi.h"

#include <memory.h>
#include <stdio.h>

typedef struct KstlListNode {
    struct KstlListNode* next;
    struct KstlListNode* prev;
} KstlListNode;

#define PAYLOAD_PTR(node) ((char*)(node) + sizeof(KstlListNode))

static void ReleaseNode(KstlList* handle, KstlListNode* node)
{
    if (!node) {
        return;
    }

    KstlListClearNode(handle, node);

    KstlRelease(node);
}

static void ReleaseHandle(KstlList* handle)
{
    if (handle->scope == LOCAL_INIT) {
        KstlRelease(handle);
    } else {
        handle->size = 0;
        handle->head = NULL;
        handle->tail = NULL;
    }
}

static KstlListNode* CreateNewNode(KstlList* handle)
{
    if (!handle) {
        return NULL;
    }

    KstlListNode* newNode = KstlAllocate(sizeof(KstlListNode) + handle->settings.itemSize);
    if (!newNode) {
        return NULL;
    }

    memset(newNode, 0, sizeof(KstlListNode) + handle->settings.itemSize);

    return newNode;
}

static int EraseNode(KstlList* handle, KstlListNode* node)
{
    int result = KstlListExtractNode(handle, node);
    if (result) {
        return -1;
    }

    ReleaseNode(handle, node);
    return 0;
}

static bool ValidateLists(KstlList* left, KstlList* right)
{
    bool isItemSizeEqual     = left->settings.itemSize == right->settings.itemSize;
    bool isDeallocFuncsEqual = left->settings.dealloc.deallocate == right->settings.dealloc.deallocate;
    bool isDeallocContextEqual =
        left->settings.dealloc.deallocatorContext == right->settings.dealloc.deallocatorContext;

    return isItemSizeEqual && isDeallocFuncsEqual && isDeallocContextEqual;
}

int KstlListInitStruct(KstlList* out, KstlListSettings settings)
{
    out->size     = 0;
    out->settings = settings;
    out->head     = NULL;
    out->tail     = NULL;
    out->scope    = EXTERNAL_INIT;
    return 0;
}

KstlList* KstlListInit(KstlListSettings settings)
{
    KstlList* handle = KstlAllocate(sizeof(KstlList));
    if (!handle) {
        return NULL;
    }

    KstlListInitStruct(handle, settings);

    handle->scope = LOCAL_INIT;

    return handle;
}

void KstlListClearNode(KstlList* handle, KstlListNode* node)
{
    if (handle->settings.dealloc.deallocate) {
        handle->settings.dealloc.deallocate(PAYLOAD_PTR(node), handle->settings.dealloc.deallocatorContext);
    }
}

void KstlListRelease(KstlList* handle)
{
    if (!handle) {
        return;
    }

    if (handle->head) {
        KstlListNode* current = handle->head;
        while (current) {
            KstlListNode* tmpNode = current->next;
            ReleaseNode(handle, current);
            current = tmpNode;
        }
    }

    ReleaseHandle(handle);
}

size_t KstlListSize(KstlList* handle)
{
    if (!handle) {
        return 0;
    }

    return handle->size;
}

bool KstlListEmpty(KstlList* handle)
{
    return KstlListSize(handle) == 0;
}

void* KstlListPushRawBack(KstlList* handle)
{
    KstlListNode* newNode = CreateNewNode(handle);
    if (!newNode) {
        return NULL;
    }

    int result = KstlListPushNodeBack(handle, newNode);
    if (result) {
        return NULL;
    }

    return PAYLOAD_PTR(newNode);
}

int KstlListPushBack(KstlList* handle, void* item)
{
    void* payload = KstlListPushRawBack(handle);
    if (!payload) {
        return -1;
    }

    memcpy(payload, item, handle->settings.itemSize);

    return 0;
}

int KstlListPushNodeBack(KstlList* handle, KstlListNode* node)
{
    if (!node) {
        return -1;
    }

    node->next = NULL;
    node->prev = NULL;

    if (handle->size == 0) {
        handle->head = node;
        handle->tail = node;
    } else {
        node->prev         = handle->tail;
        handle->tail->next = node;
        handle->tail       = node;
    }

    handle->size++;
    return 0;
}

int KstlListPushNodeFront(KstlList* handle, KstlListNode* node)
{
    if (!node) {
        return -1;
    }

    node->next = NULL;
    node->prev = NULL;

    if (handle->size == 0) {
        handle->head = node;
        handle->tail = node;
    } else {
        node->next         = handle->head;
        handle->head->prev = node;
        handle->head       = node;
    }

    handle->size++;
    return 0;
}

int KstlListPopBack(KstlList* handle)
{
    if (!handle) {
        return -1;
    }

    if (!handle->tail) {
        return -1;
    }

    KstlListNode* temp = handle->tail;
    if (handle->head == handle->tail) {
        handle->head = NULL;
        handle->tail = NULL;
    } else {
        handle->tail       = handle->tail->prev;
        handle->tail->next = NULL;
    }

    ReleaseNode(handle, temp);

    handle->size--;

    return 0;
}

void* KstlListFront(KstlList* handle)
{
    if (!handle) {
        return NULL;
    }

    if (!handle->head) {
        return NULL;
    }

    return PAYLOAD_PTR(handle->head);
}

void* KstlListBack(KstlList* handle)
{
    if (!handle) {
        return NULL;
    }

    if (!handle->tail) {
        return NULL;
    }

    return PAYLOAD_PTR(handle->tail);
}

void* KstlListPayload(KstlListNode* node)
{
    if (!node) {
        return NULL;
    }

    return PAYLOAD_PTR(node);
}

KstlListNode* KstlListPopNodeFront(KstlList* handle)
{
    if (!handle) {
        return NULL;
    }

    if (!handle->head) {
        return NULL;
    }

    KstlListNode* tmp = handle->head;
    handle->head      = handle->head->next;
    if (handle->head) {
        handle->head->prev = NULL;
    }

    if (handle->tail == tmp) {
        handle->tail = handle->head;
    }

    handle->size--;
    return tmp;
}

KstlListNode* KstlListPopNodeBack(KstlList* handle)
{
    if (!handle) {
        return NULL;
    }

    if (!handle->head) {
        return NULL;
    }

    KstlListNode* tmp = handle->tail;
    handle->tail      = handle->tail->prev;
    if (handle->tail) {
        handle->tail->next = NULL;
    }

    if (handle->head == tmp) {
        handle->head = handle->tail;
    }

    tmp->prev = NULL;
    tmp->next = NULL;

    handle->size--;
    return tmp;
}

int KstlListConcatinate(KstlList* left, KstlList* right)
{
    if (!left || !right) {
        return -1;
    }

    if (!ValidateLists(left, right)) {
        return -1;
    }

    if (KstlListEmpty(right)) {
        ReleaseHandle(right);
        return 0;
    }

    if (KstlListEmpty(left)) {
        left->head = right->head;
        left->tail = right->tail;
        left->size = right->size;
        ReleaseHandle(right);
        return 0;
    }

    right->head->prev = left->tail;
    left->tail->next  = right->head;
    left->tail        = right->tail;

    left->size += right->size;

    ReleaseHandle(right);
    return 0;
}

void* KstlListPushRawFront(KstlList* handle)
{
    KstlListNode* newNode = CreateNewNode(handle);
    if (!newNode) {
        return NULL;
    }

    if (handle->size == 0) {
        handle->head = newNode;
        handle->tail = newNode;
    } else {
        newNode->next      = handle->head;
        handle->head->prev = newNode;
        handle->head       = newNode;
    }

    handle->size++;

    return PAYLOAD_PTR(newNode);
}

int KstlListPushFront(KstlList* handle, void* item)
{
    void* payload = KstlListPushRawFront(handle);
    if (!payload) {
        return -1;
    }

    memcpy(payload, item, handle->settings.itemSize);

    return 0;
}

int KstlListExtractNode(KstlList* handle, KstlListNode* node)
{
    if (!handle) {
        return -1;
    }

    if (!node) {
        return -1;
    }

    if (handle->size == 0) {
        return -1;
    }

    if (node == handle->head) {
        KstlListPopNodeFront(handle);
        return 0;
    }

    if (node == handle->tail) {
        KstlListPopNodeBack(handle);
        return 0;
    }

    node->prev->next = node->next;
    node->next->prev = node->prev;

    node->prev = NULL;
    node->next = NULL;

    handle->size--;

    return 0;
}

int KstlListErase(KstlList* handle, KstlListIterator it)
{
    return EraseNode(handle, it.current);
}

KstlListIterator KstlListFind(KstlList* handle, void* item)
{
    KL_FOR_EACH(handle, it, end)
    {
        if (memcmp(item, KstlListIteratorValue(it), handle->settings.itemSize) == 0) {
            return it;
        }
    }

    return KstlListIteratorEnd(handle);
}

int KstlListPopFront(KstlList* handle)
{
    return EraseNode(handle, handle->head);
}

KstlListIterator KstlListIteratorBegin(KstlList* handle)
{
    KstlListIterator it = {.handle = handle, .current = handle->head, .next = handle->head ? handle->head->next : NULL};
    return it;
}

KstlListIterator KstlListIteratorEnd(KstlList* handle)
{
    KstlListIterator it = {.handle = handle, .current = NULL, .next = NULL};
    return it;
}

KstlListIterator KstlListIteratorReverseBegin(KstlList* handle)
{
    KstlListIterator it = {.handle = handle, .current = handle->tail, .next = handle->tail ? handle->tail->prev : NULL};
    return it;
}

KstlListIterator KstlListIteratorReverseEnd(KstlList* handle)
{
    KstlListIterator it = {.handle = handle, .current = NULL, .next = NULL};
    return it;
}

void KstlListIteratorNext(KstlListIterator* it)
{
    it->current = it->next;
    it->next    = it->current ? it->current->next : NULL;
}

void KstlListIteratorReverseNext(KstlListIterator* it)
{
    it->current = it->next;
    it->next    = it->current ? it->current->prev : NULL;
}

bool KstlListIteratorEqual(KstlListIterator current, KstlListIterator end)
{
    return current.handle == end.handle && current.current == end.current && current.next == end.next;
}

void* KstlListIteratorValue(KstlListIterator current)
{
    return PAYLOAD_PTR(current.current);
}